<?php
namespace Core;

use Phalcon\DI;
use Phalcon\Validation;
use Phalcon\Validation\Validator\Regex;

/**
 * Class File
 * @package Core
 */
class File
{
    /**
     * @var string
     */
    public static $root = __DIR__ . '/../';

    /**
     * 为本文件方法提供通用的参数验证，避免跨目录跨站点操作，或操作非法类型文件
     * @param $param
     * @throws \Exception
     */
    public static function paramValidation($param)
    {
        //return true;
        $validation = new Validation();
        $output = [];
        foreach ($param as $key => $value) {
            $output[$key] = $value['value'];
            switch ($value['type']) {
                case 'file':
                    $validation->add($key,
                        new Regex([
                            "message" => '不是合法文件名：<b>'.$value['value'].'</b><br />',
                            "pattern" => '/^[a-zA-Z0-9_]{3,}\.(?!php)[a-z]{2,}/',
                        ])
                    );
                    break;
                case 'dir':
                    $regex = '^(' . WEB_CODE . '|Web\/' . WEB_CODE . '|Modules|Themes)[a-zA-Z0-9\/]+$';
                    $validation->add($key,
                        new Regex([
                            "message" => '不是合法的目录<b>'.$value['value'].'</b><br />',
                            "pattern" => '/' . $regex . '/',
                        ])
                    );
                    break;
                case 'fullPath':
                    $regex = '^(' . WEB_CODE . '|Web\/' . WEB_CODE . '\/file|Modules|Themes)[a-zA-Z0-9\/]{3,}\.(?!php)[a-z]{2,}';
                    $validation->add($key,
                        new Regex([
                            "message" => '不是全文件地址<b>'.$value['value'].'</b><br />',
                            "pattern" => '/' . $regex . '/',
                        ])
                    );
                    break;
            }
        }
        $messages = $validation->validate($output);
        if ($messages->count()) {
            $error = '';
            foreach ($messages as $message) {
                $error .= $message->getMessage();
            }
            throw new \Exception($error);
        }
    }

    /**
     * 保存URL到文件
     * @param string $url
     * @param string $path
     * @return bool
     */
    public static function saveUri(string $url, $path = '')
    {
        $di = DI::getDefault();
        $config = $di->getShared('config')->get('config');
        $fileName = basename($url);
        if (empty($path)) {
            $path = $config['dir']['downloadDir'];
        }
        return self::writeFile($path, $fileName, file_get_contents($url));
    }

    /**
     * 递归创建目录
     * @param string $dir
     * @param int $mod
     * @return bool
     */
    public static function mkDir(string $dir, $mod = 0777)
    {
        self::paramValidation([
            'path' => [
                'type' => 'dir',
                'value' => $dir,
            ]
        ]);
        $path = ROOT_DIR . $dir;
        if (file_exists($path)) {
            return true;
        }
        $dirArr = explode('/', trim($dir, '/'));
        $thePath = ROOT_DIR;
        foreach ($dirArr as $da) {
            $thePath = $thePath . $da . '/';
            if (!file_exists($thePath)) {
                $state = mkdir($thePath, $mod);
                if ($state == false) {
                    return $state;
                }
            }
        }
        if (!file_exists($path)) {
            return mkdir($path, $mod);
        }
        return true;
    }

    /**
     * 创建文件
     * @param string $path
     * @param string $name
     * @param bool $mkDir
     * @return bool|string
     */
    public static function createFile(string $path, string $name, bool $mkDir = true)
    {
        self::paramValidation([
            'path' => [
                'type' => 'dir',
                'value' => $path,
            ],
            'name' => [
                'type' => 'file',
                'value' => $name,
            ]
        ]);

        $dirPath = ROOT_DIR . $path;
        $filename = ROOT_DIR . $path . $name;
        if (!file_exists($dirPath)) {
            if (!$mkDir) {
                return '目录不存在：' . $path;
            }
            self::mkDir($path);
        }
        if (!file_exists($dirPath)) {
            return '目录不存在：' . $path;
        }
        if (file_exists($filename)) {
            return "文件已经存在，请换个文件名";
        } else {
            if (!file_exists($filename)) {
                touch($name);
                rename($name, $filename);
                return true;
            } else {
                return "创建文件失败".$filename;
            }
        }
    }

    /**
     * 将内容写入制定的文件，当$bool为1，追加内容，为2则替换内容
     * @param string $path
     * @param string $name
     * @param string $content
     * @param int $bool
     * @return bool|string
     */
    function writeFile(string $path, string $name, string $content, int $bool = 2)
    {
        self::paramValidation([
            'path' => [
                'type' => 'dir',
                'value' => $path,
            ],
            'name' => [
                'type' => 'file',
                'value' => $name,
            ]
        ]);
        $filename = ROOT_DIR . $path . $name;
        if ($bool == 1) {
            if (is_writable($filename)) {
                $handle = fopen($filename, 'a');
                if (!$handle) {
                    return "文件不能打开或文件不存在";
                }
                $result = fwrite($handle, $content);
                if (!$result) {
                    return "文件写入失败";
                }
                fclose($handle);
                return true;
            } else {
                return "文件不存在";
            }
        }
        if ($bool == 2) {
            if (!file_exists($filename)) {
                self::createFile($path, $name);
                $handle = fopen($filename, 'a');
                if (fwrite($handle, $content)) ;
                return true;
            } else {
                unlink($filename);
                self::createFile($path, $name);
                self::writeFile($path, $name, $content, 1);
                return true;
            }
        }
        return false;
    }

    /**
     * 列出目录，目录名为键，相对目录为键值
     * @param string $dirName
     * @return array|string
     */
    public static function listDir(string $dirName)
    {
        self::paramValidation([
            'path' => [
                'type' => 'dir',
                'value' => $dirName,
            ],
        ]);

        $fileName = ROOT_DIR . $dirName;
        if (!file_exists($fileName)) {
            return '目录不存在：' . $dirName;
        }
        $list = scandir($fileName);
        $dirList = array();
        foreach ($list as $l) {
            if ($l[0] != '.' && is_dir(ROOT_DIR . $dirName . $l)) {
                $dirList[$l] = $dirName . $l . '/';
            }
        }
        return $dirList;
    }

    /**
     * 列出目录的详细信息
     * @param string $dir
     * @return array|string
     */
    public static function ll(string $dir)
    {
        self::paramValidation([
            'path' => [
                'type' => 'dir',
                'value' => $dir,
            ],
        ]);

        $path = ROOT_DIR . $dir;
        if (file_exists($path)) {
            echo '目录不存在：' . $dir;
        }
        $list = scandir($path);
        $output = array();
        foreach ($list as $file) {
            if ($file[0] != '.') {
                if (is_dir($path . '/' . $file)) {
                    $type = 'dir';
                    $fileArr = array('dir');
                } else {
                    $type = 'file';
                    $fileArr = explode('.', $file);
                }
                $output[] = array(
                    'path' => $dir . '/' . $file,
                    'type' => $type,
                    'fileType' => end($fileArr),
                    'url' => ltrim($dir . '/' . $file, 'public'),
                    'dir' => $dir,
                );
            }
        }
        return $output;
    }

    /**
     * 删除文件，当$r为true，则不管为空都删除
     * @param string $path
     * @param bool $r
     * @return bool|string
     */
    public static function rm(string $path, bool $r = false)
    {
        self::paramValidation([
            'path' => [
                'type' => 'fullPath',
                'value' => $path,
            ],
        ]);
        $absolutePath = ROOT_DIR . $path;
        if (!file_exists($absolutePath)) {
            return true;
        }
        if (is_dir($absolutePath) && $r) {
            if (self::clearDir($path) && rmdir($path)) {
                return true;
            } else {
                return '删除文件夹失败，可能是权限问题';
            }
        }
        if (unlink(ROOT_DIR . $path)) {
            return true;
        } else {
            return '删除失败';
        }
    }

    /**
     * 清楚缓存
     * @param string $type
     * @return array|bool
     */
    public static function clearCache(string $type = 'all')
    {
        $cacheDir = 'Web/' . WEB_CODE . '/cache/';
        $cacheList = self::listDir($cacheDir);
        $output = array();
        if ($type != 'all') {
            if (isset($cacheList[$type])) {
                if (self::clearDir($cacheDir . $type . '/') === true) {
                    return true;
                } else {
                    $output[] = '删除缓存：' . $type . '失败';
                }
            } else {
                $output[] = '缓存目录：' . $type . '不存在';
            }
        } else {
            foreach ($cacheList as $key => $value) {
                if (self::clearDir($value) === true) {
                    return true;
                } else {
                    $output[] = '缓存清除失败，请手动清除：' . $key;
                }
            }
        }
        if (empty($output)) {
            return true;
        } else {
            return $output;
        }
    }

    /**
     * 清空目录
     * @param string $dir
     * @param bool $r
     * @return array|bool
     */
    public static function clearDir(string $dir, bool $r = false)
    {
        self::paramValidation([
            'path' => [
                'type' => 'dir',
                'value' => $dir,
            ],
        ]);
        //先删除目录下的文件：
        $output = array();
        $dh = opendir(ROOT_DIR . $dir);
        while ($file = readdir($dh)) {
            if ($file != "." && $file != "..") {
                $fullpath = ROOT_DIR . $dir . "/" . $file;
                if (is_dir($fullpath) && $r) {
                    if (self::clearDir($dir . '/' . $file) === true) {
                        if (!@rmdir($fullpath)) {
                            $output[] = '删除 ' . $dir . "/" . $file . ' 失败';
                        }
                        unset($fullpath);
                    }
                } else {
                    if (!@unlink($fullpath)) {
                        $output[] = '删除 ' . $dir . "/" . $file . ' 失败';
                        break;
                    }
                }
            }
        }

        closedir($dh);
        if (empty($output)) {
            return true;
        } else {
            return $output;
        }
    }

    /**
     * 复制文件或目录
     * @param string $src
     * @param string $des
     */
    public static function cp(string $src, string $des)
    {
        self::paramValidation([
            'src' => [
                'type' => 'dir',
                'value' => $src,
            ],
            'des' => [
                'type' => 'dir',
                'value' => $des,
            ]
        ]);

        $abSrc = ROOT_DIR . $src;
        $abDes = ROOT_DIR . $des;
        $dir = opendir($abSrc);
        if (!file_exists($abDes)) {
            self::mkdir($des);
        }
        while (false !== ($file = readdir($dir))) {
            if (($file != '.') && ($file != '..')) {
                if (is_dir($abSrc . '/' . $file)) {
                    self::cp($abSrc . '/' . $file, $abDes . '/' . $file);
                } else {
                    copy($abSrc . '/' . $file, $abDes . '/' . $file);
                }
            }
        }
        closedir($dir);
    }

    /**
     * 移动文件
     * @param string $filename
     * @param string $newFile
     * @return bool|string
     */
    public function moveFile(string $filename, string $newFile)
    {
        self::paramValidation([
            'fileName' => [
                'type' => 'fullPath',
                'value' => $filename,
            ],
            'newFile' => [
                'type' => 'fullPath',
                'value' => $newFile,
            ]
        ]);
        $abFile = ROOT_DIR . $filename;
        $abNewFile = ROOT_DIR . $newFile;
        if (file_exists($abFile)) {
            $result = rename($abFile, $abNewFile);
            if ($result == false or !file_exists($newFile)) {
                return '文件移动失败';
            } else {
                return true;
            }
        }
        return '要移动的文件不存在';
    }
}
